import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * 
 * @author Paul Peasegood
 *
 */
public class Player {
	
	private String name;
	private StatCounterSpeical hp;
	private StatCounter ap; //action points
	private StatCounter energy;
	private int dodge;
	private Weapon weapon;
	private Armor armor;
	
	Socket socket;
	private DataInputStream fromServer;
	private DataOutputStream toServer;
	List<IPlayerEffect> effects = new ArrayList<IPlayerEffect>();
	private Listener listener = null;
	
	/**
	 * Creates a default player
	 */
	public Player()
	{
		this.name = "bob";
		this.hp = new StatCounterSpeical(100,1.25,15);
		this.ap = new StatCounter(100,50);
		this.energy = new StatCounter(500,2.5);
		this.dodge = 50;
		this.weapon = new Weapon();
	}
	
	/**
	 * creates a fully custom player, with no weapon
	 * @param name - players name
	 * @param hp - max HP
	 * @param apMax - max AP
	 * @param apGen - AP regeneration rate
	 * @param dodge - dodge ability
	 */
	public Player(String name, int hp,double hpGen, int hpFatigue, int apMax,int apGen,int energyMax, double energyGen, int dodge)
	{
		this.name = name;
		this.hp = new StatCounterSpeical(hp,hpGen, hpFatigue);
		this.ap = new StatCounter(apMax,apGen);
		this.energy = new StatCounter(energyMax, energyGen);
		this.dodge = dodge;
		this.weapon = null;
	}
	
	/**
	 * creates a fully custom player
	 * @param name - players name
	 * @param hp - max HP
	 * @param apMax - max AP
	 * @param apGen - AP regeneration rate
	 * @param dodge - dodge ability
	 * @param weapon - weapon
	 */
	public Player(String name, int hp, double hpGen, int hpFatigue,int apMax,int apGen,int engeryMax, int engeryGen, int dodge, Weapon weapon)
	{
		this.name = name;
		this.hp = new StatCounterSpeical(hp,hpGen, hpFatigue);
		this.ap = new StatCounter(apMax,apGen);
		this.energy = new StatCounter(engeryMax, engeryGen);
		this.dodge = dodge;
		this.weapon = weapon;
	}
	
	/**
	 * gets the HP object
	 * @return
	 */
	public StatCounter getHP()
	{
		return this.hp;
	}
	/**
	 * returns current HP total
	 * @return
	 */
	public int getCurrentHP()
	{
		return this.hp.getCurrent();
	}
	
	/**
	 * gets the HP object
	 * @return
	 */
	public StatCounter getAP()
	{
		return this.ap;
	}
	/**
	 * returns current AP total
	 * @return
	 */
	public int getCurrentAP()
	{
		return this.ap.getCurrent();
	}
	
	/**
	 * returns currently equipped weapon
	 * @return
	 */
	public Weapon getWeapon()
	{
		return this.weapon;
	}
	/**
	 * returns currently equipped armor
	 * @return
	 */
	public Armor getArmor()
	{
		return this.armor;
	}
	
	
	/**
	 * sets current HP total
	 * @param x - number HP is to be set to
	 */
	public void setHP(int x)
	{
		hp.setCurrent(x);
		System.out.println("HP:" + hp.toString());
	}
	
	/**
	 * sets current AP total
	 * @param x - number AP is to be set to
	 */
	public void setAP(int x)
	{
		ap.setCurrent(x);
		System.out.println("AP:" + ap.toString());
	}
	/**
	 * equips a new weapon
	 * @param weapon
	 */
	public void setWeapon(Weapon weapon)
	{
		this.weapon = weapon;
	}
	
	/**
	 * equips a new armor
	 * @param armor
	 */
	public void setArmor(Armor armor)
	{
		this.armor = armor;
	}
	
	/**
	 * Player takes a standard attack
	 * @param toHit - attack roll of attacker
	 * @param damage - amount of damage
	 */
	public boolean takeHit(int toHit, int damage)
	{
		//checks for armor and apples dodgePen(if applicable)
		int dodge = (armor != null) ? this.dodge-armor.getDodgePen() : this.dodge; 
		if(toHit > dodge)
		{
			takeDamage(damage,DamageType.PHYSICAL);
			System.out.println("attack hit");
			System.out.println("HP:" + hp.toString());
			return true;
		}
		else
		{
			System.out.println("attack missed");
			return false;
		}

	}
	/**
	 * takes damage, handles differently based of type
	 * @param damage
	 * @param attackType
	 */
	public void takeDamage(int damage, DamageType attackType)
	{
		if (damage < 0){ damage = 0; }//checks for negative damage
		switch(attackType)
		{
			case PHYSICAL:
				if (armor != null)
				{
					if (damage < 0){damage =0;}
					hp.decrease(armor.damReduce(damage));
					
				}
				else if (armor == null)
				{
					hp.decrease(damage);
				}
					
				break;
			case MAGIC:
			case POSION:
				this.hp.decrease(damage);
				break;
		}
		System.out.println("HP:" + hp.toString());
	}
	/**
	 * adds a new DOT effect to the player
	 * @param damage - the initial damage of the effect
	 * @param reduce - how much the damage is reduced by each turn 
	 */
	public void addDOT(int damage, double reduce)
	{
		effects.add(new DOTeffect(damage,reduce));
	}
	/**
	 * Player is healed x amount of HP
	 * @param x - amount of HP to be healed
	 */
	public void heal(int x)
	{
		if(x < 0){ x = 0;}
		hp.increase(x);
		System.out.println("HP:" + hp.toString());
	}
	/**
	 * Player HP is fully healed
	 */
	public void fullHeal()
	{
		hp.fullRestore();
		System.out.println("HP:" + hp.toString());
	}
	
	public void reload()
	{
		if (this.weapon instanceof AmmoWeapon)
		{
			((AmmoWeapon) this.weapon).reload();
		}
	}
	
	/**
	 * Attacks using currently equipped weapon, if player is able
	 * @return attack
	 */
	public Attack attack()
	{
		Attack attack;
		if (ap.getCurrent() >= weapon.getApCost() && energy.getCurrent() >= weapon.getEnergyCost())
		{
			attack = weapon.attack();
			this.ap.decrease(weapon.getApCost()); //reduces actions points per the use cost of the weapon
			this.energy.decrease(weapon.getEnergyCost()); //reduces energy per the use cost of the weapon
			System.out.println("ap:" + ap.toString());// print out remaining ap for player
			System.out.println("energy:" + energy.toString());// print out remaining ap for player
			return attack;
		}
		else
		{
			System.out.println("not enough action points");
			System.out.println("ap:" + ap.toString());// print out remaining ap for player
			System.out.println("energy:" + energy.toString());// print out remaining ap for player
			return null;
		}
			
	}
	
	/**
	 * reduces ap for movement actions
	 * @param x
	 */
	public void move(int x)
	{
		//TO DO
		if (this.ap.getCurrent() >= x)
		{
			this.ap.decrease(x);
		}
		System.out.println("AP:" + ap.toString());// print out remaining ap for player
		System.out.println("energy:" + energy.toString());// print out remaining ap for player
	}
	
	/**
	 * handles end of turn actions, e.g. regeneration
	 */
	public void endTurn()
	{
		//checks for effects and activates them if any on player
		if (!this.effects.isEmpty())
		{
			for (IPlayerEffect effect : effects)
			{	
				if (effect instanceof DOTeffect)
				{
					this.takeDamage(((DOTeffect) effect).getDamage(), DamageType.POSION);
					System.out.println(((DOTeffect) effect).getDamage() + "DOT damage");

				}
				effect.endTurn();
				
			}

		}
		this.hp.regenerate();
		this.ap.regenerate();
		this.energy.regenerate();
		System.out.println(this.toString());
	}
	
	/**
	 * connects to DM server, starts a new listner thread
	 * @param ip
	 * @param port
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public void connect(String ip, int port) throws UnknownHostException, IOException
	{
		socket = new Socket(ip, port);
		fromServer = new DataInputStream(socket.getInputStream());
		toServer = new DataOutputStream(socket.getOutputStream());
		
		listener = new Listener(socket,this);
		new Thread(listener).start();
	}
	/**
	 * disconnects from DM server
	 * @throws IOException
	 */
	public void disconnect() throws IOException
	{
		if (socket!=null){socket.close();}
	}
	
	/**
	 * sends a message to DM 
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException
	{		PrintWriter output = new PrintWriter(toServer);
		
		output.println(message);
		output.flush();
	}
	/**
	 * returns the last recived message from DM
	 * @return
	 */
	public String getMessage()
	{
		if (listener != null)
		{
			System.out.println("message=" + listener.getMessage());
			return listener.getMessage();
		}
		else
		{return null;}
	}

	/**
	 * sends attack to DM with a target
	 * @param target - number of player of DM server
	 * @throws JSONException 
	 */
	public void sendAttack(int target) throws JSONException
	{
		try
		{
			JSONObject attackString = new JSONObject();
			Attack attack = this.attack();
			attackString.put("toHit", attack.getToHit());
			attackString.put("damage",attack.getDamage());
			attackString.put("target", target);
		
			PrintWriter output = new PrintWriter(toServer);
			output.println("attack");
        
			output.println(attackString);
			output.flush();
		}
		catch(Exception e)
		{
			System.out.println("error sending attack");
		}
	}
	
	/**
	 * public toString
	 * @return
	 */
	public String toString()
	{
		return this.name + "\nHP: " + this.hp + "\nAP:" + this.ap + "\nenergy:" + this.energy
				+ "\ndodge:" + this.dodge + "\nweapon:" + this.weapon + "\narmor:" + this.armor;
	}
		
}

/**
 * 
 * @author paul peasegood
 *
 */
class Listener implements Runnable
{
	private Socket socket;
	private Player player;
	private String message;
	private String lastMessage;
	
	private  JSONObject attackString;
	
	public Listener(Socket socket, final Player player) throws IOException
	{
		this.socket = socket;
		this.player = player;
		
		System.out.println("starting listener");
	}
	
	public String getMessage()
	{
		System.out.println("last message=" + this.lastMessage);
		return this.lastMessage;
	}
	
	public void incomingAttack(String message) throws JSONException
	{
		int toHit;
		int damage;
		int DOTdam;
		int DOTred;
		
		attackString = new JSONObject(message); 
		System.out.println("incoming attack=" + attackString);
		toHit = attackString.getInt("toHit");
		damage = attackString.getInt("damage");
		if (attackString.has("DOTdam"))
		{
			DOTdam = attackString.getInt("DOTdam");
			DOTred = attackString.getInt("DOTred");
			if(player.takeHit(toHit, damage)){player.addDOT(DOTdam, DOTred);}
		}
		player.takeHit(toHit, damage);
	}
	@Override
	public void run()
	{
		try
		{
			while (true)
			{
				
				System.out.println("listening for message");
				
				DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());
				InputStreamReader inputReader = new InputStreamReader(inputFromClient);
				BufferedReader input = new BufferedReader(inputReader);
				message = input.readLine();
				
				
				switch(message)
				{
					case "endTurn":
						System.out.println("round has ended");
						player.endTurn();
						break;
					case "attack":
						message = input.readLine();
						
						incomingAttack(message);
						break;
					default:
						System.out.println(message);
							
				}
				lastMessage = message;
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}
